exitimport java.io.*;
import java.net.*;
import java.util.*;
/*******************************************************************************
 * Assertive Speech over Sockets Client-side multi-threaded application.
 * 
 * The process sits between TCP layer and end user application.
 * It may be conviently run with a command line by invoking 'main,' or used in another application.
 * Three concurrent threads are fixed that handle parsing the end user input, presenting information to the end user, and sending information to the server.
 * The fourth concurrent thread retrieves input from the server, but only exists as a usable object while the client is connected.
 ******************************************************************************/
public class ASSClientLink{
	/*<UNIQUE>*/
	/** thread to handle client output stream */
	final ClientOutputStream cout;
	/** thread to handle client input stream */
	final ClientInputStream	cin;
	/** thread to handle pushing messages to server */
	final ServerOutputStream sout;
	/** Console debugging messages */
	final PrintStream stderr;
	/*</UNIQUE>*/
	
	/*<INSTANCE>*/
	/** Current server we are connected to */
	Socket socket=null;
	/** thread to handle input from the server */
	ServerInputStream sin;
	/** user wants to continue session */
	boolean finished=false
	/** automatic command completion */;
	String autoCommand="";
	/** automatic command completion */;
	String autoUid="";
	/*</INSTANCE>*/
	
	/***************************************************************************
	 * Invoke command line session with jvm
	 **************************************************************************/
	public static void main(String args[]){
		new ASSClientLink(System.in,System.out,false);
	}/*</main>*/
	/***************************************************************************
	 * Get a new one.
	 * @param cin End user inputstream
	 * @param cout End user outputstream
	 **************************************************************************/
	public ASSClientLink(InputStream input, PrintStream output,boolean verbose){
		/* make dummy socket here? processes frequently call its methods */
		PrintStream x=null;
		if(verbose){
			x=System.out;
		}
		else{
			try{
				x=new PrintStream(System.out.toString()+".log");
			}
			catch(Exception e){
				//can't imagine why there would be a problem here
				e.printStackTrace();
				x=System.out;
				x.println("Error opening file, printing status messages to System.out");
			}
		}
		stderr=x;
		cin=new ClientInputStream(input);
		cout=new ClientOutputStream(output);
		sout=new ServerOutputStream();
		cout.start();
		cin.start();
		sout.start();
		try{socket = new Socket();}
		catch(Exception e){
			System.exit(1);
			e.printStackTrace(stderr);
		}
	}/*</ASSClientLink>*/
	/***************************************************************************
	 * Connect to the server. This method should be the only way
	 * to connect to a server.
	 * @param hostName server's hostname 
	 * @param hostPort server's welcome port
	 **************************************************************************/
	private void connect(String hostName,int hostPort){
		try{
			stderr.println("connected="+socket.isConnected());
			disconnect();
			socket=new Socket(hostName, hostPort);
			sout.attach();
			sin=new ServerInputStream();
			sin.start();
			cout.push(
				new Packet(
					"update","system"
					,"YOU ARE CONNECTED: "+socket.getInetAddress()+":"+socket.getPort()
				)
			);
		}
		catch(Exception e){
			stderr.println("APP> EXCEPTION TRYING TO CONNECT");
			cout.push(
				new Packet(
					"error"
					,"system"
					,"Failure to Connect \r\n\t\tto="+hostName+" port="+hostPort
				)
			);

			e.printStackTrace(stderr);
		}
	}/*</connect>*/
	/***************************************************************************
	 * Extra method to deal with bloody string.
	 * @param hostName server's hostname 
	 * @param hostPort server's welcome port as a string
	 **************************************************************************/
	private void connect(String hostName,String hostPort){
		hostPort=(hostPort.replace(" ","")).replace("\t","");
		try{
			// could use Integer.parseInt(s) which does not throw an exception
			connect(hostName,new Integer(hostPort));
		}
		catch(Exception e){
			stderr.println("APP> BAD PORT NUMBER\r\n   port=<"+hostPort+">");
			cout.push(new Packet("error","APP","BAD PORT NUMBER\r\n   port=<"+hostPort+">"));
		}
	}/*</connect>*/
	/***************************************************************************
	 * Kill the program.
	 **************************************************************************/
	private void exit(){
		finished=true;
		disconnect();
		try{
			// apparently cannot get it to stop blocking for input, only one solution
			System.exit(1);
			// guess is its resources are synchronized and there's nothing to be done
			// about it...  Could artifically put bytes into input?
		}
		catch(Exception e){
			e.printStackTrace(stderr);
		}
	}/*</exit>*/
	/***************************************************************************
	 * Set all global variables neccessary for disconnected state.  Puts us in
	 * 'ready to establish new connection' state.
	 **************************************************************************/
	private void disconnect(){
		if(socket.isConnected()) // must not send this if already connected, or new connection will auto-disconnect
			try{ // tell the server to disconnect
				sout.push(new Packet("disconnect","",""));
			}
			catch(Exception e){
				stderr.println("Exception thrown attempting disconnect from server");
				e.printStackTrace(stderr);
			}
		sin=null;
		try{socket.close();}
		catch(Exception e){e.printStackTrace(stderr);}
		cin.interrupt();
		cout.interrupt();
		sout.interrupt();
		/* Not sure if I need to do this */
		socket = new Socket();
	}/*</disconnect>*/
	/***************************************************************************
	 * Put a help dialog on the user console.
	 **************************************************************************/
	public void help(){
		cout.push(
			new Packet(
				"help","system",
				"\r\n  Welcome to the Assertive Speech chatroom scripting interface"
				+"\r\n   * You must prefix all commands with '@' or '$'"
				+"\r\n   * Save command state by using '@'"
				+"\r\n   * Your saved command will appear above the input box'"
				+"\r\nUsage:"
				+" \r\n '$connect hostname welcomeport'"
					+"\r\n\t\t- connect to a server hosting a session."
				+" \r\n '$disconnect' "+" - disconnect from server"
				+" \r\n '$exit' " + " - terminate the program"
				+" \r\n '$login username password' "+" - log in to session"
				+" \r\n '$register username password' "+" - create a user identity"
				+" \r\n '$logout' - log out of session"
				+" \r\n '$who' - who is in the party"
				+" \r\n '@sendall MESSAGE' - broadcast message too party"
				+" \r\n '@send username MESSAGE'"
					+"\r\n\t- send a private message too party member"
			)
		);
	}/*</help>*/
	/***************************************************************************
	 * Text goes in, convert it to a packet, queue it to the ServerOutputStream.
	 * Does no logical input checking.
	 * Instance of link has a permanent one of these attached.
	 **************************************************************************/
	private class ClientInputStream extends Thread{
		/*<INSTANCE>*/
		/***********************************************************************
		 * The input stream associated with the client.
		 **********************************************************************/
		final BufferedReader in;
		/*</INSTANCE>*/
		/***********************************************************************
		 * Built for the life of link instance.
		 * @param s The end user's input stream.
		 **********************************************************************/
		ClientInputStream(InputStream s){
			BufferedReader x=null;
			try{
				x = new BufferedReader(new InputStreamReader(s));
			}
			catch(Exception e){
				stderr.println("cin> FAILED CONSTRUCTION!!! EXITING");
				System.exit(1);
			}
			in=x;
			stderr.println("cin> CONSTRUCTED SUCESSFULLY");
		}/*</cin>*/
		/***********************************************************************
		 * The thread method. I assume it needs to be synchronized even though
		 * that violates the purpose.
		 **********************************************************************/
		public void run(){
			stderr.println("cin> EXECUTING");
			while(!finished){
				try{/* parse the end user's speech into a packet */
					dealWith();
				}
				catch(Exception e){
					stderr.println("\n\ncin> EXCEPTION THROWN");
					e.printStackTrace(stderr);
				}
			}/*</infinity>*/
			stderr.println("cin> COMPLETED, DYING");
		}/*</run>*/
		/***********************************************************************
		 * Format text input into a packet. Determine whether first token 
		 * is/not a command. ifnot a command, then sublist of the body, and
		 * then we'll construct the packet and push it independently of
		 * whether its legal.
		 *
		 * A single entry must be on the same line.
		 **********************************************************************/
		private synchronized void dealWith() throws Exception{
			String v[]=in.readLine().split(" ");
			if(v[0].startsWith("@")||v[0].startsWith("$")){
				String c=v[0].substring(0,1).toLowerCase();
				v[0]=v[0].substring(1).toLowerCase();//spaces taken care of
				if( // <command> <uid> [body]
					v[0].equals("connect")	||	v[0].equals("register")||
					v[0].equals("login"  )		||	v[0].equals("send")){
					String arg1="";
					String arg2="";
					if(v.length>1)
						arg1=v[1];
					arg2=cat(v,2);
					sout.push(new Packet(v[0],arg1,arg2));
					/* Save State */
					if(c.equals("@")&&v[0].equals("send")){
						autoCommand="send";
						autoUid=arg1;
					}
					stderr.println(
						"cin> YOU SAID\n\t"
						+ (new Packet(v[0],arg1,arg2)).toDelimitedString()
					);
				}//if
				else if(v[0].equals("help")){
					help();
				}
				else if( // <command> [uid] [body]
						// because uid and body are basically nonsense in the context
					v[0].equals("disconnect")	||v[0].equals("exit")||
					v[0].equals("logout")			||v[0].equals("who")){
					sout.push(new Packet(v[0],"",""));
				}
				else if(v[0].equals("sendall")){
						// <command> [body] # uid is of no use
					sout.push(new Packet("sendall","",cat(v,1)));
					/* Save State */
					if(c.equals("@")){
						autoCommand="sendall";
						autoUid="";
					}
					stderr.println(
						"cin> YOU SAID\n\t"
						+ (new Packet("sendall","",cat(v,1))).toDelimitedString()
					);
				}
				else{
					cout.push(new Packet("error","system","command not supported, see help if you are confused"));
				}
			}//if starts with @ or $
			else{
				sout.push(new Packet(autoCommand,autoUid,cat(v,0)));
			}
		}/*</dealWith>*/
		private String cat(String[] v,int n){
			//stderr.print("cat: n="+n);
			//for(int i=0;i<v.length;i++)
			//	stderr.print(" "+v[i]);
			if(!(n<v.length)||v.length<1)
				return "";
			String s=v[n];
			for(n++;n<v.length;n++)
				s=s+" "+v[n];
			return s;
		}/*</cat>*/
	}/*</cin>*/
	/*******************************************************************
	 * Takes the packets out of the buffer.  Calls output checking
	 * operations which will send messages to the client process
	 * if there is error found in the end user's logic.
	 * 
	 * Instance of ASSClientLink must always have existing instance of
	 * ServerOutputStream in order to handle messages directed at the
	 * application.
	 * 
	 * Put this thing's data should be outside the scope of the class.  Never
	 * seems to have trouble accessing shared resources.
	 ******************************************************************/
	class ServerOutputStream extends Thread{
		/*<INSTANCE>*/
		final Buffer<Packet> sbuff;
		private DataOutputStream out;
		/*</INSTANCE>*/
		/***********************************************************************
		 * Null constructor. Allocates its buffer.
		 **********************************************************************/
		ServerOutputStream(){
			sbuff=new Buffer<Packet>();
			stderr.println("\nsout> CONSTRUCTED BUFFER...");
			//sbuff.queue(new Packet("BADCOMMAND","admin","Body of the packet, contains a string"));
		}/*</sout>*/
		/***********************************************************************
		 * Construct output stream so communication can begin
		 **********************************************************************/
		public void attach(){
			try{
				out=new DataOutputStream(socket.getOutputStream());
			}
			catch(Exception e){
				stderr.println(
					"\nsout> Failed to connect to server "+"\n\t"
					+socket.getInetAddress() + " port=" + socket.getPort());
			}
		}/*</attach>*/
		/***********************************************************************
		 * Put packet onto the buffer to the server
		 **********************************************************************/
		public boolean push(Packet r){
			boolean x=sbuff.queue(r);
			sout.interrupt();
			stderr.println("sout> pushed\n\t"+r.toDelimitedString());
			return x;
		}/*</push>*/
		/***********************************************************************
		 * Continue processing while the socket has not been closed.
		 * Implies we need a way to generate a fresh one in some cases.
		 * 
		 * Method synchronized=> doesn't throw exception trying to wait
		 **********************************************************************/
		public synchronized void run(){
			//out = new DataOutputStream(System.out);
			stderr.println("\nsout> EXECUTED...");
			while(!finished){
				stderr.println("sout> BUFFER:"+ sbuff.size());
				Packet q;
				while((q=sbuff.deque())!=null)
					dealWith(q);/*</while>*/
				/* Real Solution: run the thread non-stop */
				try{sout.wait();}//sleep(5000);}
				catch(InterruptedException e){
					stderr.println("\nsout> IRQ");
				}
				catch(Exception e){
					stderr.println("\nsout> ERROR: Unknown Exception");
					e.printStackTrace(stderr);
				}
			}/*</while>*/
			stderr.println("sout> COMPLETED, DYING");
		}/*</run>*/
		/***********************************************************************
		 * I can probably get away with assuming the packet's contents are
		 * always legal.
		 **********************************************************************/
		private void dealWith(Packet q){
			if(!sendToApplication(q))
				sendToServer(q);
		}/*</dealWith>*/
		/***********************************************************************
		 * Interprets whether the command is application directed, if so takes
		 * appropriate action specified by the user, and returns true.  If
		 * packet not directed at application, then returns false and does
		 * nothing.
		 **********************************************************************/
		private boolean sendToApplication(Packet p){
			String applicationCommands[]={"connect","disconnect","exit","help"};
			if(p.command.equals(applicationCommands[0])){
				stderr.println("sout> CALLING CONNECT ");
				/* call global connect method 
					<hostname or ip><port no> */
				connect(p.uid,p.body);
				return true;
			}
			else if(p.command.equals(applicationCommands[1])){
				/* call disconnect method */
				stderr.println("sout> CALLING DISCONNECT ");
				//stderr.println("\n"+p.toString()+"\n");
				disconnect();
				return true;
			}
			else if(p.command.equals(applicationCommands[2])){
				/* call exit method */
				stderr.println("sout> CALLING exit ");
				//stderr.println("\n"+p.toString()+"\n");
				exit();
				return true;
			}
			/* not a legal command*/
			else if(p.command.equals(applicationCommands[2])){
				stderr.println("sout> CALLING help ");
				help();
			}
			else if(p.command.equals("")){
				cout.push(
					new Packet(
						"error","system"
						,"Illegal command attempted, see tutorial if you are confused "
					)
				);
				return true;
			}
			return false;
		}/*</sendToApplication>*/
		/***********************************************************************
		 * Assumes the command is directed at the server.  Does some logical
		 * error checking, decides whether or not to send it, and takes
		 * the appropriate action.
		 **********************************************************************/
		private boolean sendToServer(Packet p){
			stderr.println(
				"\n\nsout> Too TCP:\n\t "+ p.toDelimitedString()
				//+"\n********************************************\n"
				//+"\n********************************************\n"
			);
			try{	/* push the packet */
				if(socket.isConnected())
					out.writeBytes(p.toString());
				else{
					cout.push(new Packet("error","system","You are not connected to a chat server, see tutorial if you are confused"));
					throw new Exception("NOT CONNECTED TO SERVER");
				}
			}
			catch(Exception e){
				stderr.println(
					"\nsout> COULD NOT DELIVER TO SERVER\n\t"
					+p.toDelimitedString());
				e.printStackTrace(stderr);
			}
			return true;
		}/*</sendToServer>*/
	}/*<ServerOutputStream>*/
	/***************************************************************************
	 * Packets come in from server, queue to outputstream
	 **************************************************************************/
	private class ServerInputStream extends Thread{
		/*<INSTANCE>*/
		/** It's deciated input stream */
		final private BufferedReader in;
		/*</INSTANCE>*/
		public ServerInputStream()throws Exception{
			in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
		}/*</ServerInputStream>*/
		/***********************************************************************
		 * Packets come in from server, then they are queued to client's
		 * outputstream. This thread's existence explicitly depends on the
		 * socket's connection state.
	     **********************************************************************/
		public void run(){
			stderr.println("sin> STARTING...");
			while(socket.isConnected()){
				/* put packet on cout's queue*/
				try{
					Packet s = new Packet(in);
					cout.push(s);
				}
				catch(Exception e){
					/* If an exception is thrown right here,
							it must imply the connect is broken */
					stderr.println("sin> FATAL EXCEPTION: "+socket.isClosed());
					e.printStackTrace(stderr);
					disconnect();
				}
			}
			stderr.println("sin> Connection to server terminated");
			stderr.println("sin> COMPLETED, DYING");
		}/*</run>*/
	}/*</ServerInputStream>*/
	/***************************************************************************
	 * Convert packets in queue to an end user readable String and put them
	 * onto the stream. There is one persistent instance of this thread for
	 * one instance of a link (so make it final).
	 **************************************************************************/
	private class ClientOutputStream extends Thread{
		/*<INSTANCE>*/
		final private Buffer<Packet> cbuff;
		final private PrintStream out;
		/*</INSTANCE>*/
		/***********************************************************************
		 * Refer output to the stream, construct a new empty message buffer.
		 * @param cout The end user's output stream.
		 **********************************************************************/
		private ClientOutputStream(PrintStream cout){
				out=cout;
				cbuff=new Buffer<Packet>();
		}/*</cout>*/
		/***********************************************************************
		 * Convert's packet to string and puts on client's screen.
		 * This thread will live for as long as the link does.  I don't believe
		 * synchronization to be the correct course of action, but it made an
		 * infinite throw go away.
		 **********************************************************************/
		public synchronized void run(){
			stderr.println("cout> STARTING...");
			while(!finished){
				Packet r;
				while((r=cbuff.deque())!=null){
					/* convert packet into text, push to client*/;
					out.println(getPredicate(r) + "> " + r.body );
				}
				try{cout.wait();}
				catch(InterruptedException e){
					stderr.println("cout> IRQ");
				}
				catch(Exception e){
					e.printStackTrace(stderr);
				}
			}/*</while>*/
			stderr.println("cout> COMPLETED, DYING");
		}/*</run>*/
		/***************************************************************
		 * Interprets the meaning of the packet for the user
		 * @param r Response packet to the client.
		 **************************************************************/
		private String getPredicate(Packet r){
			if(r.command.equals("mail"))
				return r.uid + " whispers";
			if(r.command.equals("send"))
				return "you said to " + r.uid;
			if(r.command.equals("sendall"))
				return r.uid + " says";
			if(r.command.equals("login"))
				return r.uid;
			return "\r\n"+ r.uid + "(" + r.command + ")"; // includes "logout" "who" "help"
		}/*</getSender>*/
		/***************************************************************
		 * Put a packet onto the client's buffer
		 * @param r Response packet to the client.
		 **************************************************************/
		private boolean push(Packet r){
			boolean x=cbuff.queue(r);
			if(x)/* notify the thread */
				cout.interrupt();
			return x;
		}/*</queue>*/
	}/*</cout>*/
}/*</ASSClientLink>*/
